
package org.platform.tool.util;

import cn.hutool.core.io.FileUtil;
import it.sauronsoftware.ftp4j.FTPClient;
import it.sauronsoftware.ftp4j.FTPException;
import it.sauronsoftware.ftp4j.FTPFile;
import it.sauronsoftware.ftp4j.FTPIllegalReplyException;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;


/**
 * TTP客户端工具
 */
@Slf4j
public final class FtpClientUtil {
    public static final String REMOTE_ROOT_PATH = "/";
    private FTPClient client;
    private String host;
    private int port;
    private String username;
    private String password;

    private FtpClientUtil(String host, int port, String username, String password) {
        this.host = host;
        this.port = port;
        this.username = username;
        this.password = password;

        connect();
    }


    private FtpClientUtil(String host, String username, String password) {
        this(host, 21, username, password);  // default port 21
    }

    public static FtpClientUtil getInstance(String host, String username, String password) {
        return new FtpClientUtil(host, username, password);
    }

    public static FtpClientUtil getInstance(String host, int port, String username, String password) {
        return new FtpClientUtil(host, port, username, password);
    }

    /**
     * 创建FTP连接
     */
    public void connect() {
        client = new FTPClient();
        try {
            client.connect(host, port);
            client.login(username, password);
        } catch (Exception e) {
            String ftpStr = String.format("host:%s,port%s,username%s,password%s", host, port, username, password);
            throw new RuntimeException(String.format("无法连接到FTP:%s," + e.getMessage(), ftpStr));
        }
    }

    /**
     * FTP下载文件到本地一个文件夹,如果本地文件夹不存在，则创建必要的目录结构
     *
     * @param remoteFileName  FTP文件
     * @param localFolderPath 存的本地目录
     * @return
     */
    public void download(String remoteFileName, String localFolderPath, String localFolderSub) {
        File file = new File(remoteFileName);
        //本地文件存储更目录+文件路径中去除不需要的部分
        localFolderSub = localFolderSub.replaceAll("/", "\\\\\\\\");
        localFolderPath = localFolderPath + file.getParent().replaceFirst(localFolderSub, "");
        File localFolder = new File(localFolderPath);
        if (localFolder.isFile()) {
            throw new RuntimeException("所要的下载保存的地方是一个文件，无法保存！");
        } else {
            if (!localFolder.exists()) {
                localFolder.mkdirs();
            }
        }
        String localfilepath = PathUtil.formatPath4File(localFolderPath + File.separator
                + file.getName());
        try {
            File localFile = new File(localfilepath);
            client.download(remoteFileName, localFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 下载ftp指定的文件到本地目录,同时修改文件名
     *
     * @param remoteFileName  FTP文件路径
     * @param localFolderPath 本地目录
     * @param fileName        本地文件名,无后缀名
     * @return
     */
    public void downloadFile(String remoteFileName, String localFolderPath, String fileName) {
        File localFolder = new File(localFolderPath);
        if (localFolder.isFile()) {
            throw new RuntimeException("所要的下载保存的地方是一个文件，无法保存！");
        } else {
            if (!localFolder.exists()) {
                if (!localFolder.mkdirs()) {
                    throw new RuntimeException("目录创建失败,请检测目录正确性: " + localFolderPath);
                }
            }
        }
        //获取真实的后缀名
        String suffix = remoteFileName.substring(remoteFileName.lastIndexOf('.'), remoteFileName.length());

        String localFilePath;
        //判断目录是否完整 比如: path/path1/
        if (localFolderPath.endsWith("/") || localFolderPath.endsWith("\\")) {
            localFilePath = PathUtil.formatPath4File(localFolderPath + fileName);
        } else {
            localFilePath = PathUtil.formatPath4File(localFolderPath + File.separator + fileName);
        }

        try {
            File localFile = new File(localFilePath + ".tmp");
            client.download(remoteFileName, localFile);

            //修改为为源文件的真实文件类型
            FileUtil.rename(localFile, fileName + suffix, false, true);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }


    /**
     * FTP下载文件到本地一个文件夹,如果本地文件夹不存在，则创建必要的目录结构
     *
     * @param remoteFileName  FTP文件
     * @param localFolderPath 存的本地目录
     * @return
     */
    public void download(String remoteFileName, String localFolderPath, String localFolderSub, String fileName) {
        File file = new File(remoteFileName);
        //本地文件存储更目录+文件路径中去除不需要的部分
        localFolderPath = localFolderPath + file.getParent().replaceFirst(localFolderSub, "");
        File localFolder = new File(localFolderPath);
        if (localFolder.isFile()) {
            throw new RuntimeException("所要的下载保存的地方是一个文件，无法保存！");
        } else {
            if (!localFolder.exists()) {
                localFolder.mkdirs();
            }
        }
        String localfilepath = PathUtil.formatPath4File(localFolderPath + File.separator
                + fileName);
        try {
            File localFile = new File(localfilepath);
            client.download(remoteFileName, localFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * FTP下载文件到本地一个文件夹,如果本地文件夹不存在，则创建必要的目录结构
     *
     * @param remoteFileName FTP文件
     * @return
     */
    public void downloadDirFtpFile(String remoteFileName, List<String> ftpList) {
        int x = isExist(remoteFileName);
        try {
            if (x == FTPFile.TYPE_DIRECTORY) {
                //再本地创建和FTP上一致的文件目录
                File fileRem = new File(remoteFileName);
                //File file = new File(localFolderPath);
                /*if (!file.exists()) {
                    file.mkdirs();
                }*/
                //输入需要下载的目录
                client.changeDirectory(remoteFileName);
                FTPFile[] list = client.list();
                for (FTPFile ftpFile : list) {
                    //判断是否是文件，是文件直接下载，是目录调用下载目录
                    if (ftpFile.getType() == FTPFile.TYPE_FILE) {
                        ftpList.add(client.currentDirectory() + "/" + ftpFile.getName());
                        //download(ftpFile.getName(), localFolderPath);
                    } else {
                        downloadDirFtpFile(ftpFile.getName(), ftpList);
                        //当前目录中文件下载完成后，返回上级目录
                        client.changeDirectoryUp();
                    }
                }
            } else {
                throw new RuntimeException("所要下载的文件目录" + remoteFileName + "不存在！");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public List<String> listFileNames(String remoteFileName) {
        List<String> ftpList = new ArrayList<>();
        downloadDirFtpFile(remoteFileName, ftpList);

        return ftpList;
    }

    public void delete(String fileUrl) {
        try {
            client.deleteFile(fileUrl);
        } catch (Exception e) {
            throw new RuntimeException("无法删除FTP文件:" + fileUrl);
        }
    }

    /**
     * FTP上传本地文件到FTP的一个目录下
     *
     * @param localFile        本地文件
     * @param remoteFolderPath FTP上传目录
     */
    public void upload(File localFile, String remoteFolderPath) {

        remoteFolderPath = PathUtil.formatPath4FTP(remoteFolderPath);
        // FtpListener listener = FtpListener.instance(FTPOptType.UP);
        FtpListener listener = FtpListener.instance();
        try {
            client.changeDirectory(remoteFolderPath);
            if (!localFile.exists()) {
                throw new RuntimeException("所要上传的FTP文件" + localFile.getPath() + "不存在！");
            }
            if (!localFile.isFile()) {
                throw new RuntimeException("所要上传的FTP文件" + localFile.getPath() + "是一个文件夹！");
            }
            if (listener != null) {
                client.upload(localFile, listener);
            } else {
                client.upload(localFile);
            }
            client.changeDirectory("/");
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(String.format("无法上传文件localFile：%s，remoteFolderPath：%", localFile, remoteFolderPath));
        }
    }

    /**
     * FTP上传本地文件到FTP的一个目录下
     *
     * @param localFilePath    本地文件路径
     * @param remoteFolderPath FTP上传目录
     */
    public void upload(String localFilePath, String remoteFolderPath) {
        File localfile = new File(localFilePath);
        upload(localfile, remoteFolderPath);
    }

    public void upload(String remoteName, File localFile, String remoteFolderPath) {
        remoteFolderPath = PathUtil.formatPath4FTP(remoteFolderPath);
        FtpListener listener = FtpListener.instance();
        InputStream inputStream = null;
        try {
            client.changeDirectory(remoteFolderPath);
            inputStream = new FileInputStream(localFile);
            client.upload(remoteName, inputStream, 0, 0, listener);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(String.format("无法上传文件 remoteName：%s，localFile：%s，remoteFolderPath：%s", remoteName, localFile, remoteFolderPath));
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Throwable t) {
                }
            }
        }
    }

    /**
     * 同一个目录下第二次之后上传文件方法
     *
     * @param remoteName
     * @param localFile
     */
    public void upload(String remoteName, File localFile) {
        FtpListener listener = FtpListener.instance();
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(localFile);
            client.upload(remoteName, inputStream, 0, 0, listener);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(String.format("无法上传文件 remoteName：%s，localFile：%s，remoteFolderPath：%s", remoteName, localFile));
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Throwable t) {
                }
            }
        }
    }

    public void uploadLinux(String remoteName, File localFile, String remoteFolderPath) {
        remoteFolderPath = PathUtil.formatPath4FTP(remoteFolderPath);
        FtpListener listener = FtpListener.instance();
        InputStream inputStream = null;
        try {
            if (!"/".equals(remoteFolderPath)) {
                client.changeDirectory(remoteFolderPath);
            }
            inputStream = new FileInputStream(localFile);
            client.upload(remoteName, inputStream, 0, 0, listener);
        } catch (Exception e) {
            throw new RuntimeException(String.format("无法上传文件 remoteName：%s，localFile：%s，remoteFolderPath：%s", remoteName, localFile, remoteFolderPath));
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Throwable t) {
                }
            }
        }
    }

    /**
     * 批量上传本地文件到FTP指定目录上
     *
     * @param localFilePaths   本地文件路径列表
     * @param remoteFolderPath FTP上传目录
     */
    public void uploadListPath(List<String> localFilePaths, String remoteFolderPath) {
        remoteFolderPath = PathUtil.formatPath4FTP(remoteFolderPath);
        try {
            client.changeDirectory(remoteFolderPath);
            // FtpListener listener = FtpListener.instance(FTPOptType.UP);
            FtpListener listener = FtpListener.instance();
            for (String path : localFilePaths) {
                File file = new File(path);
                if (!file.exists()) {
                    throw new RuntimeException("所要上传的FTP文件" + path + "不存在！");
                }
                if (!file.isFile()) {
                    throw new RuntimeException("所要上传的FTP文件" + path + "是一个文件夹！");
                }
                if (listener != null) {
                    client.upload(file, listener);
                } else {
                    client.upload(file);
                }
            }
            client.changeDirectory("/");
        } catch (Exception e) {
            throw new RuntimeException(String.format("无法批量上传文件 localFilePaths：%s，remoteFolderPath：%s", localFilePaths, remoteFolderPath));
        }
    }

    /**
     * 批量上传本地文件到FTP指定目录上
     *
     * @param localFiles       本地文件列表
     * @param remoteFolderPath FTP上传目录
     */
    public void uploadListFile(List<File> localFiles, String remoteFolderPath) {
        try {
            client.changeDirectory(remoteFolderPath);
            remoteFolderPath = PathUtil.formatPath4FTP(remoteFolderPath);
            // FtpListener listener = FtpListener.instance(FTPOptType.UP);
            FtpListener listener = FtpListener.instance();
            for (File file : localFiles) {
                if (!file.exists()) {
                    throw new RuntimeException("所要上传的FTP文件" + file.getPath() + "不存在！");
                }
                if (!file.isFile()) {
                    throw new RuntimeException("所要上传的FTP文件" + file.getPath() + "是一个文件夹！");
                }
                if (listener != null) {
                    client.upload(file, listener);
                } else {
                    client.upload(file);
                }
            }
            client.changeDirectory("/");
        } catch (Exception e) {
            throw new RuntimeException(String.format("无法批量上传文件 localFilePaths：%s，remoteFolderPath：%s", localFiles, remoteFolderPath));
        }
    }

    /**
     * 设置FTP根目录 默认为
     */
    public void rootChangeDirectory() {
        try {
            client.changeDirectory("/");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (FTPIllegalReplyException e) {
            e.printStackTrace();
        } catch (FTPException e) {
            e.printStackTrace();
        }

    }

    /**
     * 判断一个FTP路径是否存在，如果存在返回类型(FTPFile.TYPE_DIRECTORY=1、FTPFile.TYPE_FILE=0、
     * FTPFile.TYPE_LINK=2) 如果文件不存在，则返回一个-1
     *
     * @param remotePath FTP文件或文件夹路径
     * @return 存在时候返回类型值(文件0 ， 文件夹1 ， 连接2)，不存在则返回-1
     */
    public int isExist(String remotePath) {
        remotePath = PathUtil.formatPath4FTP(remotePath);
        int x = -1;
        FTPFile[] list = null;
        try {
            list = client.list(remotePath);
        } catch (Exception e) {
            return -1;
        }
        if (list.length > 1) {
            return FTPFile.TYPE_DIRECTORY;
        } else if (list.length == 1) {
            FTPFile f = list[0];
            if (f.getType() == FTPFile.TYPE_DIRECTORY) {
                return FTPFile.TYPE_DIRECTORY;
            }
            // 假设推理判断
            String _path = remotePath + "/" + f.getName();
            try {
                int y = client.list(_path).length;
                if (y == 1) {
                    return FTPFile.TYPE_DIRECTORY;
                } else {
                    return FTPFile.TYPE_FILE;
                }
            } catch (Exception e) {
                return FTPFile.TYPE_FILE;
            }
        } else {
            try {
                client.changeDirectory(remotePath);
                return FTPFile.TYPE_DIRECTORY;
            } catch (Exception e) {
                return -1;
            }
        }
    }

    /**
     * 关闭FTP连接，关闭时候像服务器发送一条关闭命令
     *
     * @return 关闭成功，或者链接已断开，或者链接为null时候返回true，通过两次关闭都失败时候返回false
     */

    public boolean disconnect() {
        if (client == null) {
            return true;
        }
        if (client.isConnected()) {
            try {
                client.disconnect(true);
                return true;
            } catch (Exception e) {
                try {
                    client.disconnect(false);
                } catch (Exception e1) {
                    e1.printStackTrace();
                    return false;
                }
            }
        }
        return true;
    }


    public List<FTPFile> listFiles(String remotePath) {
        try {
            FTPFile[] files = client.list(remotePath);
            List<FTPFile> list = new ArrayList<FTPFile>();
            for (FTPFile ftpFile : files) {
                String name = ftpFile.getName();
                if (name.equals(".") || name.equals("..")) {
                    continue;
                }
                list.add(ftpFile);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 修改FTP中文件名
     *
     * @param oldPath 源文件名
     * @param newPath 修改后文件名
     * @return
     */
    public boolean renameFile(String oldPath, String newPath) {
        boolean flag = false;
        try {
            if (client != null) {

                client.rename(oldPath, newPath);
                flag = true;
            }
        } catch (Exception e) {
            log.error("文件移动/重命名失败,原因:", e);
        }
        return flag;
    }
}